package com.ler.yrmall.coupon.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ler.yrmall.coupon.dto.SeckillOrderDTO;
import com.ler.yrmall.coupon.dto.SeckillSkuDTO;
import com.ler.yrmall.basic.Form;
import com.ler.yrmall.basic.Result;
import com.ler.yrmall.coupon.entity.SeckillEntity;
import com.ler.yrmall.coupon.entity.SeckillSkuRelationEntity;
import com.ler.yrmall.coupon.form.SubmitSeckillForm;
import com.ler.yrmall.coupon.mapper.SeckillMapper;
import com.ler.yrmall.coupon.service.SeckillService;
import com.ler.yrmall.coupon.service.SeckillSkuRelationService;
import com.ler.yrmall.coupon.vo.SeckillVO;
import com.ler.yrmall.coupon.vo.SeckillSkuRelationVO;
import com.ler.yrmall.enums.BasicStatusEnum;
import com.ler.yrmall.product.api.ProductApi;
import com.ler.yrmall.product.dto.SkuDTO;
import com.ler.yrmall.utils.SecurityUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * @author leron
 */
@Service
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, SeckillEntity> implements SeckillService {

    @Resource
    private SeckillSkuRelationService seckillSkuRelationService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @DubboReference
    private ProductApi productApi;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private RedissonClient redissonClient;

    public static final String SESSION_PREFIX = "seckill:session:";

    public static final String SECKILL_SKU_PREFIX = "seckill:sku:";

    public static final String SECKILL_STOCK_PREFIX = "seckill:stock:";

    @Override
    public IPage<SeckillEntity> find(Form request) {
        return this.page(request.getPage(),Wrappers.<SeckillEntity>lambdaQuery()
                .like(request.getKeyword() != null, SeckillEntity::getName,request.getKeyword())
                .orderByDesc(SeckillEntity::getStartTime));
    }


    @Override
    public List<SeckillVO> getLatest3DaysSeckill() {
        LocalDateTime current = LocalDateTime.now();
        List<SeckillEntity> sessionList = this.list(Wrappers.<SeckillEntity>lambdaQuery()
                .between(SeckillEntity::getStartTime, current, current.plusDays(3))
                .eq(SeckillEntity::getStatus, BasicStatusEnum.SystemEnableStatus.ENABLED.getCode()));
        if (!sessionList.isEmpty()) {
            return sessionList.stream().map(session -> {
                SeckillVO vo = BeanUtil.copyProperties(session, SeckillVO.class);
                List<SeckillSkuRelationEntity> seckillSkuRelationEntities =
                        seckillSkuRelationService.list(Wrappers.<SeckillSkuRelationEntity>lambdaQuery().eq(SeckillSkuRelationEntity::getSeckillId, session.getId()));
                vo.setSeckillSkuRelationList(BeanUtil.copyToList(seckillSkuRelationEntities, SeckillSkuRelationVO.class));
                return vo;
            }).collect(Collectors.toList());
        }
        return Collections.emptyList();

    }

    @Override
    @PostConstruct
    // todo 定时任务检测
    public void uploadSeckillSkuLatest3Days() {
        List<SeckillVO> sessions = this.getLatest3DaysSeckill();
        // 缓存活动信息
        // TODO 设置过期时间
        sessions.forEach(session -> {
            String key = SESSION_PREFIX + session.getId();
            List<SeckillSkuRelationVO> skus = session.getSeckillSkuRelationList();
            session.setSeckillSkuRelationList(null);
            stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(session));
            // 缓存商品信息
            BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKU_PREFIX + session.getId());
            skus.forEach(vo -> {
                Result<SkuDTO> result = productApi.getSkuById(vo.getSkuId());
                SkuDTO data = result.getData();
                SeckillSkuDTO seckillSkuDTO = BeanUtil.copyProperties(data, SeckillSkuDTO.class);
                Assert.isTrue(result.isSuccess(),() -> new RuntimeException("商品获取异常"));
                BeanUtil.copyProperties(vo, seckillSkuDTO);
                seckillSkuDTO.setStartTime(session.getStartTime());
                seckillSkuDTO.setSeckillPrice(vo.getPrice());
                seckillSkuDTO.setEndTime(session.getEndTime());
                // 随机码 如果直接是商品id，很容易受到攻击，随机码只有在秒杀开始时放出
                String token = UUID.fastUUID().toString(true);
                seckillSkuDTO.setRandomCode(token);
                // 分布式信号量
                RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_STOCK_PREFIX + token);
                semaphore.trySetPermits(seckillSkuDTO.getCount());
                hashOps.put(String.valueOf(vo.getSkuId()),JSON.toJSONString(seckillSkuDTO));
            });
        });
    }

    @Override
    public List<SeckillSkuDTO> getSeckillInfo() {
        Set<String> keys = stringRedisTemplate.keys(SESSION_PREFIX + "*");
        if (CollectionUtil.isEmpty(keys)) {
            return Collections.emptyList();
        }
        LocalDateTime current = LocalDateTime.now();
        for (String item : keys) {
            String sessionStr = stringRedisTemplate.opsForValue().get(item);
            SeckillVO session = JSON.parseObject(sessionStr, SeckillVO.class);
            if (session == null) continue;
            if (current.isAfter(session.getStartTime()) && current.isBefore(session.getEndTime())) {
                // 当前场次id
                BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKU_PREFIX + session.getId());
                List<String> values = hashOps.values();
                return Optional.ofNullable(values).map(List::stream).orElse(Stream.empty()).map(s -> JSON.parseObject(s,SeckillSkuDTO.class)).collect(Collectors.toList());
            }
        }
        return null;
    }

    @Override
    public SeckillSkuDTO getSkuSeckill(Long skuId) {
        Set<String> keys = stringRedisTemplate.keys(SESSION_PREFIX + "*");
        if (CollectionUtil.isEmpty(keys)) {
            return null;
        }
        LocalDateTime current = LocalDateTime.now();
        for (String item : keys) {
            String sessionStr = stringRedisTemplate.opsForValue().get(item);
            SeckillVO session = JSON.parseObject(sessionStr, SeckillVO.class);
            if (session == null) continue;
            if (current.isAfter(session.getStartTime()) && current.isBefore(session.getEndTime())) {
                BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKU_PREFIX + session.getId());
                String skuStr = hashOps.get(String.valueOf(skuId));
                return JSON.parseObject(skuStr, SeckillSkuDTO.class);
            }
        }
        return null;
    }

    @Override
    public String submitSeckillSku(SubmitSeckillForm submitSeckillForm) {
        String sessionStr = stringRedisTemplate.opsForValue().get(SESSION_PREFIX + submitSeckillForm.getSeckillId());
        SeckillVO session = JSON.parseObject(sessionStr, SeckillVO.class);
        if (session == null) {
            throw new RuntimeException("秒杀已结束!");
        }
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SECKILL_SKU_PREFIX + submitSeckillForm.getSeckillId());
        String skuStr = hashOps.get(String.valueOf(submitSeckillForm.getSkuId()));
        if (skuStr == null) return null;
        LocalDateTime now = LocalDateTime.now();
        SeckillSkuDTO sku = JSON.parseObject(skuStr, SeckillSkuDTO.class);
        // todo 异常枚举
        if (now.isBefore(session.getStartTime()) || now.isAfter(session.getEndTime())) return null;
        if (!sku.getRandomCode().equals(submitSeckillForm.getRandomCode())) return null;
        // todo 限购验证
        RSemaphore semaphore = redissonClient.getSemaphore(SECKILL_STOCK_PREFIX + submitSeckillForm.getRandomCode());
        boolean flag = semaphore.tryAcquire(submitSeckillForm.getNum());
        if (!flag) return null;
        String orderNo = IdWorker.getTimeId();
        SeckillOrderDTO order = new SeckillOrderDTO();
        order.setSeckillId(submitSeckillForm.getSeckillId());
        order.setSeckillPrice(sku.getPrice());
        order.setNum(submitSeckillForm.getNum());
        order.setOrderNo(orderNo);
        order.setMemberId(SecurityUtils.getLoginUser().getId());
        order.setSkuId(submitSeckillForm.getSkuId());
        rocketMQTemplate.syncSend("seckill_order", MessageBuilder.withPayload(order).build());
        return orderNo;
    }
}